
#include <chardev.h>
#include <kerrno.h>
#include <kmalloc.h>
#include <types.h>
#include <list.h>
#include <vfs.h>
#include <fs/fs.h>
#include <fs/devfs.h>

struct chardev_class
{
   /** Major/minor for the device */
  struct fs_dev_id_t dev_id;

  struct chardev_ops   *ops;

  /** This corresponds to the chardev_class_custom_data field passed
      to open/read/etc. and to chardev_register_class() */
  void                     *custom_data;

  __u32               ref_cnt; 

  struct chardev_class *next, *prev;
};



/** The list of registered classes, ie the dictionary major number ->
    device class description */
static struct  chardev_class *registered_chardev_classes;

/**
 * Return the device descriptionn structure for the corresponding
 * device class, or NULL when none found.
 */
static struct chardev_class * lookup_chardev_class(__u32 device_class)
{
  struct  chardev_class *chardev;
  int nb;

  list_foreach (registered_chardev_classes, chardev, nb)
    {
      if (chardev->dev_id.device_class == device_class)
	return chardev;
    }

  return NULL;
}


int chardev_unregister_class (__u32 device_class)
{
  struct  chardev_class *chardev;

  /* Make sure this device class is already registered */
  chardev = lookup_chardev_class(device_class);
  if (NULL == chardev)
    return -ENODEV;

  /* Make sure no files are already opened for it */
  if (chardev->ref_cnt != 1)
    return -EBUSY;

  /* remove it from the list */
  list_delete (registered_chardev_classes, chardev);
  return kfree((__u32)chardev);
}


static int chardev_new_opened_file(open_file_descriptor * this)
{
  int retval;


   struct fs_dev_id_t *dev_id = this->inode->dev_id; 

  /* Lookup the character device description structure */
  struct chardev_class * chardev
    = lookup_chardev_class(dev_id->device_class);
  if (NULL == chardev)
    return -ENODEV;

  chardev->ref_cnt ++;


  /* Call the open callback */
  retval = chardev->ops->open(this, chardev->custom_data);
  if (OK != retval)
    {
      chardev->ref_cnt --;
      return retval;
    }

 

  return retval;
}

/**
 * Callback called to read the contents of the opened file: call the
 * read method of the device driver.
 */
static int chardev_wrap_read(open_file_descriptor *this,
				   void * dest_buf,
				   __u32 len)
{
  int retval = -ENOSYS;
   struct fs_dev_id_t *dev_id = this->inode->dev_id; 

  /* Lookup the character device description structure */
  struct chardev_class * chardev
    = lookup_chardev_class(dev_id->device_class);
  if (NULL == chardev)
    return -ENODEV;

  if (NULL != chardev->ops->read)
    retval = chardev->ops->read(this, dest_buf, &len);
  
  return retval;
}


/**
 * Callback called to write bytes to the opened file: call the write
 * method of the device driver.
 */
static int chardev_wrap_write(open_file_descriptor *this,
				   void * src_buf,
				   __u32 len)
{
  int retval = -ENOSYS;

 struct fs_dev_id_t *dev_id = this->inode->dev_id; 

  /* Lookup the character device description structure */
  struct chardev_class * chardev
    = lookup_chardev_class(dev_id->device_class);
  if (NULL == chardev)
    return -ENODEV;


  if (NULL != chardev->ops->write)
    retval = chardev->ops->write(this, src_buf,&len);
   
  return retval;
}

static int
chardev_close_opened_file(open_file_descriptor * this)
{


int retval;


   struct fs_dev_id_t *dev_id = this->inode->dev_id; 

  /* Lookup the character device description structure */
  struct chardev_class * chardev
    = lookup_chardev_class(dev_id->device_class);
  if (NULL == chardev)
    return -ENODEV;

  if (NULL != chardev->ops->close)
  /* Call the open callback */
  retval = chardev->ops->close(this, chardev->custom_data);
  else
    retval = OK;
  if (OK != retval)
    return retval;

  chardev->ref_cnt --;

  return retval;

}


static chardev_interfaces chardev_map_ops = { 
	.read = chardev_wrap_read,
	.write = chardev_wrap_write,
	.open = chardev_new_opened_file,
	.close = chardev_close_opened_file,
        .ioctl = NULL
};

int  chardev_register_class (const char* name,__u32 device_class,
				      struct chardev_ops *ops,
				      void * chardev_class_custom_data)
{
  struct chardev_class *chardev;

  /* Make sure this device class is not already registered */
  chardev = lookup_chardev_class(device_class);
  if (NULL != chardev)
    return -EBUSY;

  /* Allocate and initialize a new device description */
  chardev = (struct chardev_class *) kmalloc(sizeof(struct chardev_class), 0);
  if (chardev == NULL)
    return -ENOMEM;

  chardev->dev_id.device_class = device_class;
  chardev->custom_data  = chardev_class_custom_data;
  chardev->ops          = ops;
  chardev->ref_cnt      = 1;

  /* insert it into the list */
  list_add_tail (registered_chardev_classes, chardev);

   devfs_register_chardev(name,  &chardev_map_ops, (struct fs_dev_id_t *) &chardev->dev_id);

  return OK;
}


